import os.path
import shutil
import tkinter

from tkinter import filedialog, messagebox, HORIZONTAL

import cv2
import numpy
import scipy
from PIL import Image, ImageTk


class PhotoShop:
    def __init__(self):
        self.root = tkinter.Tk()
        self.root.title('图片编辑器')
        self.root.configure(bg='#FFFFFF')
        self.root.geometry("1200x715")
        self.root.resizable(width=False, height=False)
        self.build()  # 生成组件
        self.layout()  # 布局
        self.temp('begin')  # 初始化temp文件夹
        self.root.protocol("WM_DELETE_WINDOW", lambda: self.temp('end'))  # 退出时删除temp文件夹
        self.root.mainloop()

    def build(self):
        # 左部图片显示区
        self.lab_showPhoto = tkinter.Label(self.root, borderwidth=1, relief='sunken')

        # 下部分图片选择按钮和展示路径
        self.lab_line = tkinter.Label(self.root, bg='#D9D9D9')
        self.btn_choice_photo = tkinter.Button(self.root, font=('微软雅黑', 10), text='选择图片', bg='#FFFFFF',
                                               border=0, command=self.choice_photo)
        self.pathVar = tkinter.StringVar()
        self.pathVar.set('未选择图片!')
        self.lab_path = tkinter.Label(self.root, font=('微软雅黑', 10), bg='#FFFFFF', textvariable=self.pathVar,
                                      anchor='w')
        self.btn_reset = tkinter.Button(self.root, font=('微软雅黑', 10), text='恢复原图', bg='#FFFFFF',
                                        border=0, command=self.photo_reset)
        # 右部分功能选择区
        self.lab_func = tkinter.Label(self.root, bg='#185ABD')
        self.btn_pthotoBinary = tkinter.Button(self.root, font=('微软雅黑', 10), text='二值化', bg='#F8FFE0',
                                               border=0, command=self.Binary)
        self.btn_photoGray = tkinter.Button(self.root, font=('微软雅黑', 10), text='灰度图', bg='#F8FFE0',
                                            border=0, command=self.Gray)
        self.btn_photoEdgedetection = tkinter.Button(self.root, font=('微软雅黑', 10), text='边缘检测', bg='#F8FFE0',
                                                     border=0)
        self.btn_photoDilate = tkinter.Button(self.root, font=('微软雅黑', 10), text='膨胀', bg='#F8FFE0',
                                              border=0, command=lambda: self.DilateAndErode('Dilate'))
        self.btn_photoErode = tkinter.Button(self.root, font=('微软雅黑', 10), text='腐蚀', bg='#F8FFE0',
                                             border=0, command=lambda: self.DilateAndErode('Erode'))
        self.btn_photoEmboss = tkinter.Button(self.root, font=('微软雅黑', 10), text='浮雕', bg='#F8FFE0',
                                              border=0, command=self.Emboss)
        self.sca_photoBright = tkinter.Scale(self.root, font=('微软雅黑', 10), label='亮度调节', bg='#F8FFE0',
                                             from_=-255, to=255, resolution=1, orient=HORIZONTAL)
        self.sca_photoContrast = tkinter.Scale(self.root, font=('微软雅黑', 10), label='对比度调节', bg='#F8FFE0',
                                               from_=0, to=10, resolution=0.1, orient=HORIZONTAL)
        self.sca_photoBlur = tkinter.Scale(self.root, font=('微软雅黑', 10), label='模糊调节', bg='#F8FFE0',
                                           from_=0, to=5, resolution=1, orient=HORIZONTAL)
        # 预览调节按钮
        self.btn_view = tkinter.Button(self.root, font=('微软雅黑', 10), text='预览调节', bg='#F8FFE0',
                                       activebackground='#FFDC00',
                                       border=0, command=self.photoAdjust)
        # 保存按钮
        self.btn_save = tkinter.Button(self.root, font=('微软雅黑', 10), text='保存图片', bg='#F8FFE0',
                                       border=0, command=self.save)

    def layout(self):
        self.lab_showPhoto.place(x=10, y=10, height=665, width=900)
        self.btn_choice_photo.place(x=0, y=676, height=40, width=100)
        self.lab_path.place(x=120, y=676, height=40, width=500)
        self.btn_reset.place(x=1100, y=676, height=40, width=100)

        # 功能区
        self.lab_func.place(x=920, y=10, width=270, height=675)
        self.btn_pthotoBinary.place(x=945, y=20, height=50, width=60)
        self.btn_photoGray.place(x=1025, y=20, height=50, width=60)
        self.btn_photoEdgedetection.place(x=1105, y=20, height=50, width=60)

        self.btn_photoDilate.place(x=945, y=90, height=50, width=60)
        self.btn_photoErode.place(x=1025, y=90, height=50, width=60)
        self.btn_photoEmboss.place(x=1105, y=90, height=50, width=60)

        self.sca_photoBright.place(x=945, y=260, width=220)
        self.sca_photoContrast.place(x=945, y=340, width=220)
        self.sca_photoBlur.place(x=945, y=420, width=220)

        self.btn_view.place(x=980, y=600, height=40, width=70)
        self.btn_save.place(x=1060, y=600, height=40, width=70)

    def choice_photo(self):  # 选择原图
        path = filedialog.askopenfilename(filetypes=(("图片", "*.jpg"),))
        if path == "":
            return
        self.pathVar.set(path)
        global originalPhoto, latestPhoto  # 全局变量：原图和最新图
        img = Image.open(path)
        resize_img = self.resize(900, 675, img)
        originalPhoto = ImageTk.PhotoImage(resize_img)
        self.lab_showPhoto['image'] = originalPhoto
        self.sca_photoContrast.set(1)  # 设置对比度初始值
        latestPhoto = img

    def resize(self, w_, h_, jpg_image):  # 对图片进行resize，使其保持原比例在Label显示 by博客园
        w, h = jpg_image.size
        f1 = 1.0 * w_ / w
        f2 = 1.0 * h_ / h
        factor = min([f1, f2])
        width = int(w * factor)
        height = int(h * factor)
        return jpg_image.resize((width, height), Image.ANTIALIAS)

    def photo_reset(self):
        if self.isChoice():
            self.lab_showPhoto['image'] = originalPhoto

    def save(self):
        if self.isChoice():
            global latestPhoto
            savename = filedialog.asksaveasfilename(filetypes=(("图片", "*.jpg"),))
            if savename == "":
                return
            latestPhoto.save(savename)

    def isChoice(self):  # 判断是否选择图片
        if self.pathVar.get() == "未选择图片!":
            self.display_messagebox('提示', '未选择图片！')
            return False
        else:
            return True

    def cv_imread(self, file_path):  # 解决imread路径包含中文的问题 by知乎
        cv_img = cv2.imdecode(numpy.fromfile(file_path, dtype=numpy.uint8), -1)
        return cv_img

    def photoAdjust(self):  # 调节图片亮度，对比度，模糊
        if self.isChoice():
            filename = os.path.basename(self.pathVar.get())
            img = self.cv_imread(self.pathVar.get())
            a = self.sca_photoContrast.get()
            b = self.sca_photoBright.get()
            r = self.sca_photoBlur.get()

            # 亮度和对比度
            new_photo = numpy.zeros(img.shape, numpy.uint8)
            for i in range(img.shape[0]):
                for j in range(img.shape[1]):
                    for k in range(img.shape[2]):
                        color = a * img[i, j, k] + b
                        if color > 255:
                            color = 255
                        elif color < 0:
                            color = 0
                        new_photo[i, j, k] = color
            if r != 0:
                r = r * 2 + 1
                sigma = 0.3 * ((r - 1) * 0.5 - 1) + 0.8
                kernel = self.getWeight(r, sigma)
                for i in range(new_photo.shape[2]):
                    new_photo[..., i] = scipy.signal.convolve2d(new_photo[..., i], kernel, mode="same",
                                                                boundary="fill",
                                                                fillvalue=0)
                new_photo = numpy.array(new_photo, dtype=numpy.uint8)

            cv2.imwrite('temp/temp_new_' + filename, new_photo)

            global newPhoto, latestPhoto
            newPhoto = Image.open(os.path.abspath('temp/temp_new_' + filename))
            latestPhoto = newPhoto
            newPhoto = self.resize(900, 675, newPhoto)
            newPhoto = ImageTk.PhotoImage(newPhoto)
            self.lab_showPhoto['image'] = newPhoto

    def Binary(self):  # 二值化
        if self.isChoice():
            filename = os.path.basename(self.pathVar.get())

            if not os.path.exists('temp/temp_Binary_' + filename):  # 先在temp文件夹中寻找，减少重复运算
                img = self.cv_imread(self.pathVar.get())
                gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                r, dst = cv2.threshold(gray_img, 140, 255, cv2.THRESH_BINARY)
                cv2.imwrite('temp/temp_Gray_' + filename, gray_img)
                cv2.imwrite('temp/temp_Binary_' + filename, dst)

            global newPhoto, latestPhoto
            newPhoto = Image.open(os.path.abspath('temp/temp_Binary_' + filename))
            latestPhoto = newPhoto
            newPhoto = self.resize(900, 675, newPhoto)
            newPhoto = ImageTk.PhotoImage(newPhoto)
            self.lab_showPhoto['image'] = newPhoto

    def Gray(self):  # 灰度图
        if self.isChoice():
            filename = os.path.basename(self.pathVar.get())

            if not os.path.exists('temp/temp_Gary_' + filename):
                img = self.cv_imread(self.pathVar.get())
                gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                cv2.imwrite('temp/temp_Gray_' + filename, gray_img)

            global newPhoto, latestPhoto
            newPhoto = Image.open(os.path.abspath('temp/temp_Gray_' + filename))
            latestPhoto = newPhoto
            newPhoto = self.resize(900, 675, newPhoto)
            newPhoto = ImageTk.PhotoImage(newPhoto)
            self.lab_showPhoto['image'] = newPhoto

    def Emboss(self):  # 浮雕效果
        if self.isChoice():
            filename = os.path.basename(self.pathVar.get())
            if not os.path.exists('temp/temp_Gray_' + filename):
                # 生成灰度图
                img = self.cv_imread(self.pathVar.get())
                gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                cv2.imwrite('temp/temp_Gray_' + filename, gray_img)

            if not os.path.exists('temp/temp_Emboss_' + filename):
                # 浮雕处理
                gray_img = cv2.imread('temp/temp_Gray_' + filename, -1)
                print(gray_img.shape)
                kernel = numpy.array([[1, 0, 0],
                                      [0, 0, 0],
                                      [0, 0, -1]], dtype=numpy.uint8)
                emboss_photo = numpy.zeros([gray_img.shape[0], gray_img.shape[1]], dtype=numpy.uint8)
                emboss_photo = scipy.signal.convolve2d(gray_img, kernel, mode="same", boundary="fill",
                                                       fillvalue=0) + 120
                cv2.imwrite('temp/temp_Emboss_' + filename, emboss_photo)
            # 展示
            global newPhoto, latestPhoto
            newPhoto = Image.open(os.path.abspath('temp/temp_Emboss_' + filename))
            latestPhoto = newPhoto
            newPhoto = self.resize(900, 675, newPhoto)
            newPhoto = ImageTk.PhotoImage(newPhoto)
            self.lab_showPhoto['image'] = newPhoto

    def DilateAndErode(self, type):  # 膨胀与膨胀
        if self.isChoice():
            filename = os.path.basename(self.pathVar.get())
            global newPhoto, latestPhoto
            # 生成二值图
            if not os.path.exists('temp/temp_Gray_' + filename):
                img = self.cv_imread(self.pathVar.get())
                gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                cv2.imwrite('temp/temp_Gray_' + filename, gray_img)
            if not os.path.exists('temp/temp_Binary_' + filename):
                gray_img = cv2.imread('temp/temp_Gray_' + filename, -1)
                r, dst = cv2.threshold(gray_img, 140, 255, cv2.THRESH_BINARY)
                cv2.imwrite('temp/temp_Binary_' + filename, dst)
            if not os.path.exists('temp/temp_Dilate_' + filename) or not os.path.exists('temp/temp_Erode_' + filename):
                binary_photo = cv2.imread('temp/temp_Binary_' + filename)
                kernel = numpy.ones((3, 3), numpy.uint8)
                if type == 'Dilate':
                    dilate_photo = cv2.dilate(binary_photo, kernel, iterations=1)  # 膨胀操作
                    cv2.imwrite('temp/temp_Dilate_' + filename, dilate_photo)
                elif type == 'Erode':
                    erode_photo = cv2.erode(binary_photo, kernel, iterations=1)  # 腐蚀操作
                    cv2.imwrite('temp/temp_Erode_' + filename, erode_photo)
            if type == 'Dilate':
                newPhoto = Image.open(os.path.abspath('temp/temp_Dilate_' + filename))
            elif type == 'Erode':
                newPhoto = Image.open(os.path.abspath('temp/temp_Erode_' + filename))
            latestPhoto = newPhoto
            newPhoto = self.resize(900, 675, newPhoto)
            newPhoto = ImageTk.PhotoImage(newPhoto)
            self.lab_showPhoto['image'] = newPhoto

    def temp(self, flag):  # temp文件夹控制
        if flag == 'begin':
            if os.path.exists('temp'):
                shutil.rmtree('temp')
            os.mkdir('temp')
        elif flag == 'end':
            shutil.rmtree('temp')
            self.root.destroy()

    def getWeight(self, r, sigma):  # 获取二维正态分布权重
        kernel = numpy.zeros(shape=(r, r), dtype=numpy.float)
        radius = r // 2
        for y in range(-radius, radius + 1):
            for x in range(-radius, radius + 1):
                res = 1.0 / (2.0 * numpy.pi * sigma ** 2) * numpy.exp((x ** 2 + y ** 2) / (2 * sigma ** 2) * -1)
                kernel[y + radius, x + radius] = res
        kernel = kernel / numpy.sum(kernel)
        return kernel

    def display_messagebox(self, title, message):  # 弹窗
        tkinter.messagebox.showinfo(title=title, message=message)
